#include "CCoinMechImpl.h"
#include <QSerialPort>
#include <iomanip>

#include "glog/logging.h"
#include "CommonType.h"

using namespace zl;

struct CCoinMechImpl::DatalinkImpl
{
    QSerialPort serial;
};

CCoinMechImpl::CCoinMechImpl()
    : impl_(nullptr)
    , init_flag_(false)
    , enable_flag_(false)
    , status_(0)
{

}

CCoinMechImpl::~CCoinMechImpl()
{

}

int32_t CCoinMechImpl::Initialize(const std::string &com, int32_t baud, IValidatorObserver *obs)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "initialize coin mech: " << com << "@" << baud;

    if (impl_ != nullptr)
    {
        LOG(WARNING) << "device is already initialized";
        return zl::EResult_Failed;
    }

    impl_ = new DatalinkImpl();
    impl_->serial.setPortName(com.c_str());
    impl_->serial.setBaudRate(baud);
    auto ret = impl_->serial.open(QIODevice::ReadWrite);
    if (!ret)
    {
        LOG(ERROR) << "initialize coin mech failed, open serial port failed: " << ret;
        delete impl_;
        impl_ = nullptr;
        return zl::EResult_Failed;
    }

    // connect
    connect(&impl_->serial, SIGNAL(readyRead()), this, SLOT(event_device_responsed()));
    init_flag_ = true;
    obs_ = obs;
    return zl::EResult_Success;
}

int32_t CCoinMechImpl::Uninitialize()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "uninitialize coin mech";

    if (impl_ == nullptr || !init_flag_)
    {
        LOG(WARNING) << "coin mech is not initialized";
        return zl::EResult_Failed;
    }

    impl_->serial.close();
    delete impl_;
    impl_ = nullptr;
    init_flag_ = false;
    return zl::EResult_Success;
}

int32_t CCoinMechImpl::Status()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "coin mech status: " << status_;

    return status_;
}

int32_t CCoinMechImpl::Reset()
{
    return zl::EResult_Success;
}

int32_t CCoinMechImpl::Setup()
{
    cmd_disable();
    return zl::EResult_Success;
}

int32_t CCoinMechImpl::Enable()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "coin mech enable";

    if (impl_ == nullptr || !init_flag_)
    {
        LOG(ERROR) << "coin mech is not initialized";
        return zl::EResult_Failed;
    }

    // send enable command
    auto ret = cmd_enable();
    if (ret != 0)
    {
        LOG(ERROR) << "enable coin mech failed: " << ret;
        return zl::EResult_Failed;
    }

    status_ = 1;
    enable_flag_ = true;
    return zl::EResult_Success;
}

int32_t CCoinMechImpl::Disable()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "coin mech disable";

    if (impl_ == nullptr || !init_flag_)
    {
        LOG(ERROR) << "coin mech is not initialized";
        return zl::EResult_Failed;
    }

    auto ret = cmd_disable();
//    auto ret = cmd_test();
    if (ret != 0)
    {
        LOG(ERROR) << "send disable command failed: " << ret;
        return zl::EResult_Failed;
    }

    status_ = 0;
    enable_flag_ = false;
    return zl::EResult_Success;
}

int32_t CCoinMechImpl::cmd_enable()
{
    LOG(INFO) << "send cmd enable";

    // cmd
    constexpr size_t cmd_len = 5;
    unsigned char cmd[cmd_len]{ 0x00 };
    cmd[0] = 0x90;
    cmd[1] = 0x05;
    cmd[2] = 0x01;
    cmd[3] = 0x03;
    cmd[4] = 0x9A;
    auto cnt = impl_->serial.write((const char*)cmd, cmd_len);
    if (cnt != cmd_len)
    {
        LOG(ERROR) << "send cmd failed, write serial port failed";
        return -1;
    }

    return 0;
}

int32_t CCoinMechImpl::cmd_disable()
{
    LOG(INFO) << "send cmd enable";

    // cmd
    constexpr size_t cmd_len = 5;
    unsigned char cmd[cmd_len]{ 0x00 };
    cmd[0] = 0x90;
    cmd[1] = 0x05;
    cmd[2] = 0x02;
    cmd[3] = 0x03;
    cmd[4] = 0x99;
    auto cnt = impl_->serial.write((const char*)cmd, cmd_len);
    if (cnt != cmd_len)
    {
        LOG(ERROR) << "send cmd failed, write serial port failed";
        return -1;
    }

    return 0;
}

int32_t CCoinMechImpl::cmd_status()
{
    LOG(INFO) << "send cmd get status";

    // cmd
    constexpr size_t cmd_len = 5;
    unsigned char cmd[cmd_len]{ 0x00 };
    cmd[0] = 0x90;
    cmd[1] = 0x05;
    cmd[2] = 0x50;
    cmd[3] = 0x03;
    cmd[4] = 0xE8;
    auto cnt = impl_->serial.write((const char*)cmd, cmd_len);
    if (cnt != cmd_len)
    {
        LOG(ERROR) << "send cmd failed, write serial port failed";
        return -1;
    }

    return 0;
}

int32_t CCoinMechImpl::process_received_package(const QByteArray &pkg)
{
    if (pkg.isEmpty())
    {
        return -1;
    }

    LOG(INFO) << "new device pkg: " << pkg.toHex().toStdString();
    uint8_t code = pkg[2];
    switch (code)
    {
    case 0x11: // 硬币器被查询
    case 0x14: // 硬币器被禁用
        LOG(INFO) << "device is disabled";
        break;
    case 0x12: // 硬币事件
    {
        int channel = pkg[3];
        LOG(INFO) << "device coin event, channel: " << channel;
        if (obs_ != nullptr && channel == 1)
        {
            obs_->EventCredit(1 * 100, "COIN");
        }
        break;
    }
    default:
        return 0;
    }

    return 0;
}

void CCoinMechImpl::event_device_responsed()
{
    if (impl_ == nullptr || !init_flag_)
    {
        return;
    }

    static QByteArray _buffer{ 0x00 };
    auto data = impl_->serial.readAll();
    _buffer.append(data);
    LOG(INFO) << "coin device read: " << data.toHex().toStdString();

    int32_t require_len = 0;
    while (_buffer.size() > 4)
    {
        if (_buffer[0] = 0x90)
        {
            require_len = _buffer[1];
        }

        if (require_len > _buffer.size())
        {
            return;
        }

        auto pkg = _buffer.left(require_len);
        _buffer.remove(0, require_len);
        process_received_package(pkg);
    }
}
